﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UCodeFirstData.cs" company="CodePlex">
//   The MIT License
//   Copyright (c) 2013 Julien Blin
//   
//   Permission is hereby granted, free of charge, to any person obtaining a copy
//   of this software and associated documentation files (the "Software"), to deal
//   in the Software without restriction, including without limitation the rights
//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//   copies of the Software, and to permit persons to whom the Software is
//   furnished to do so, subject to the following conditions:
//   
//   The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//   
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//   THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace UCodeFirst
{
    using System;
    using System.Linq;

    using Umbraco.Core;
    using Umbraco.Core.Logging;

    /// <summary>
    /// Manages persistence concerns for uCodeFirst.
    /// </summary>
    public static class UCodeFirstData
    {
        /// <summary>
        /// True indicates that the database has been verified, false otherwise.
        /// </summary>
        private static bool ensuredDb;

        /// <summary>
        /// Lock for the <see cref="EnsureDb"/> method execution.
        /// </summary>
        private static volatile object ensuredDbLock = new object();

        /// <summary>
        /// Gets the value associated with the <typeparamref name="T"/> and <paramref name="key"/>.
        /// Returns null if not found.
        /// </summary>
        /// <typeparam name="T">
        /// The type.
        /// </typeparam>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <returns>
        /// The value, or null if not found.
        /// </returns>
        public static string GetValue<T>(string key)
        {
            return GetValue(typeof(T), key);
        }

        /// <summary>
        /// Gets the value associated with the <paramref name="type"/> and <paramref name="key"/>.
        /// Returns null if not found.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <returns>
        /// The value, or null if not found.
        /// </returns>
        public static string GetValue(Type type, string key)
        {
            EnsureDb();
            try
            {
                var db = ApplicationContext.Current.DatabaseContext.Database;
                return db.SingleOrDefault<string>(
                    Queries.GetValueByTypeAndKey,
                    new object[] { new { Type = type.Name, Key = key } });
            }
            catch (Exception ex)
            {
                throw new UCodeFirstException(Resources.ErrorWhileAccessingDb, ex);
            }
        }

        /// <summary>
        /// Sets the value associated with the <typeparamref name="T"/> and <paramref name="key"/>.
        /// </summary>
        /// <typeparam name="T">
        /// The type.
        /// </typeparam>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        public static void SetValue<T>(string key, string value)
        {
            SetValue(typeof(T), key, value);
        }

        /// <summary>
        /// Sets the value associated with the <paramref name="type"/> and <paramref name="key"/>.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="key">
        /// The key.
        /// </param>
        /// <param name="value">
        /// The value.
        /// </param>
        public static void SetValue(Type type, string key, string value)
        {
            EnsureDb();
            try
            {
                var db = ApplicationContext.Current.DatabaseContext.Database;
                db.Execute(
                    db.SingleOrDefault<int>(Queries.CountByTypeAndKey, new { Type = type.Name, Key = key }) > 0
                        ? Queries.UpdateValueByTypeAndKey
                        : Queries.InsertValueByTypeAndKey,
                    new object[] { new { Type = type.Name, Key = key, Value = value } });
            }
            catch (Exception ex)
            {
                throw new UCodeFirstException(Resources.ErrorWhileAccessingDb, ex);
            }
        }

        /// <summary>
        /// Deletes the value associated with the <typeparamref name="T"/> and <paramref name="key"/>.
        /// </summary>
        /// <typeparam name="T">
        /// The type.
        /// </typeparam>
        /// <param name="key">
        /// The key.
        /// </param>
        public static void DeleteValue<T>(string key)
        {
            DeleteValue(typeof(T), key);
        }

        /// <summary>
        /// Deletes the value associated with the <paramref name="type"/> and <paramref name="key"/>.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="key">
        /// The key.
        /// </param>
        public static void DeleteValue(Type type, string key)
        {
            EnsureDb();
            try
            {
                var db = ApplicationContext.Current.DatabaseContext.Database;
                db.Execute(Queries.DeleteValueByTypeAndKey, new { Type = type.Name, Key = key });
            }
            catch (Exception ex)
            {
                throw new UCodeFirstException(Resources.ErrorWhileAccessingDb, ex);
            }
        }

        /// <summary>
        /// Deletes the values associated with the <typeparamref name="T"/>
        /// and where the key ends with <paramref name="keyEnd"/>.
        /// </summary>
        /// <typeparam name="T">
        /// The type.
        /// </typeparam>
        /// <param name="keyEnd">
        /// The key end.
        /// </param>
        public static void DeleteValuesWithKeyThatEndsWith<T>(string keyEnd)
        {
            DeleteValuesWithKeyThatEndsWith(typeof(T), keyEnd);
        }

        /// <summary>
        /// Deletes the values associated with the <paramref name="type"/>
        /// and where the key ends with <paramref name="keyEnd"/>.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="keyEnd">
        /// The key end.
        /// </param>
        public static void DeleteValuesWithKeyThatEndsWith(Type type, string keyEnd)
        {
            EnsureDb();
            try
            {
                var db = ApplicationContext.Current.DatabaseContext.Database;
                db.Execute(Queries.DeleteValueByTypeAndKeyLike, new { Type = type.Name, KeyEnd = string.Format("%{0}", keyEnd) });
            }
            catch (Exception ex)
            {
                throw new UCodeFirstException(Resources.ErrorWhileAccessingDb, ex);
            }
        }

        /// <summary>
        /// Make sure that the table has been created.
        /// </summary>
        private static void EnsureDb()
        {
            if (ensuredDb)
            {
                return;
            }

            lock (ensuredDbLock)
            {
                if (ensuredDb)
                {
                    return;
                }

                if (!ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured)
                {
                    throw new UCodeFirstException(Resources.DatabaseNotConfigured);
                }

                try
                {
                    var db = ApplicationContext.Current.DatabaseContext.Database;
                    if (db.Query<int>(Queries.TableExists).FirstOrDefault() == 0)
                    {
                        LogHelper.Info(typeof(UCodeFirstData), Resources.CreatingTableUCodeFirst);
                        db.Execute(Queries.CreateTable);
                    }

                    ensuredDb = true;
                }
                catch (Exception ex)
                {
                    throw new UCodeFirstException(Resources.ErrorWhileAccessingDb, ex);
                }
            }
        }
    }
}
